#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using static QRCoder.ArtQRCode;
using static QRCoder.QRCodeGenerator;

// pull request raised to extend library used. 
namespace QRCoder
{
#if NET6_0_WINDOWS
    [System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
    public class ArtQRCode : AbstractQRCode, IDisposable
    {
        /// <summary>
        /// Constructor without params to be used in COM Objects connections
        /// </summary>
        public ArtQRCode() { }

        /// <summary>
        /// Creates new ArtQrCode object
        /// </summary>
        /// <param name="data">QRCodeData generated by the QRCodeGenerator</param>
        public ArtQRCode(QRCodeData data) : base(data) { }

        /// <summary>
        /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
        /// </summary>
        /// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
        /// <returns>QRCode graphic as bitmap</returns>
        public Bitmap GetGraphic(int pixelsPerModule)
        {
            return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent);
        }

        /// <summary>
        /// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
        /// </summary>
        /// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
        /// <returns>QRCode graphic as bitmap</returns>
        public Bitmap GetGraphic(Bitmap backgroundImage = null)
        {
            return this.GetGraphic(10, Color.Black, Color.White, Color.Transparent, backgroundImage: backgroundImage);
        }

        /// <summary>
        /// Renders an art-style QR code with dots as modules and various user settings
        /// </summary>
        /// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
        /// <param name="darkColor">Color of the dark modules</param>
        /// <param name="lightColor">Color of the light modules</param>
        /// <param name="backgroundColor">Color of the background</param>
        /// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
        /// <param name="pixelSizeFactor">Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.</param>
        /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
        /// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
        /// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
        /// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
        /// <returns>QRCode graphic as bitmap</returns>
        public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Bitmap backgroundImage = null, double pixelSizeFactor = 0.8, 
                                 bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted, 
                                 BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null)
        {
            if (pixelSizeFactor > 1)
                throw new Exception("The parameter pixelSize must be between 0 and 1. (0-100%)");
            int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule / pixelSizeFactor));

            var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
            var offset = (drawQuietZones ? 0 : 4);
            var size = numModules * pixelsPerModule;

            var bitmap = new Bitmap(size, size);

            using (var graphics = Graphics.FromImage(bitmap))
            {
                using (var lightBrush = new SolidBrush(lightColor))
                {
                    using (var darkBrush = new SolidBrush(darkColor))
                    {
                        // make background transparent
                        using (var brush = new SolidBrush(backgroundColor))                        
                            graphics.FillRectangle(brush, new Rectangle(0, 0, size, size));
                        //Render background if set
                        if (backgroundImage != null)
                        {
                            if (backgroundImageStyle == BackgroundImageStyle.Fill)
                                graphics.DrawImage(Resize(backgroundImage, size), 0, 0);
                            else if (backgroundImageStyle == BackgroundImageStyle.DataAreaOnly)
                            {
                                var bgOffset = 4 - offset;
                                graphics.DrawImage(Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)), 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule));
                            }
                        }
                            

                        var darkModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, darkBrush);
                        var lightModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, lightBrush);

                        for (var x = 0; x < numModules; x += 1)
                        {
                            for (var y = 0; y < numModules; y += 1)
                            {
                                var rectangleF = new Rectangle(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule);

                                var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x];
                                var solidBrush = pixelIsDark ? darkBrush : lightBrush;
                                var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;

                                if (!IsPartOfFinderPattern(x, y, numModules, offset))
                                    if (drawQuietZones && quietZoneRenderingStyle == QuietZoneStyle.Flat && IsPartOfQuietZone(x, y, numModules))
                                        graphics.FillRectangle(solidBrush, rectangleF);
                                    else
                                        graphics.DrawImage(pixelImage, rectangleF);
                                else if (finderPatternImage == null)
                                    graphics.FillRectangle(solidBrush, rectangleF);
                            }
                        }
                        if (finderPatternImage != null)
                        {
                            var finderPatternSize = 7 * pixelsPerModule;
                            graphics.DrawImage(finderPatternImage, new Rectangle(0, 0, finderPatternSize, finderPatternSize));
                            graphics.DrawImage(finderPatternImage, new Rectangle(size - finderPatternSize, 0, finderPatternSize, finderPatternSize));
                            graphics.DrawImage(finderPatternImage, new Rectangle(0, size - finderPatternSize, finderPatternSize, finderPatternSize));
                        }
                        graphics.Save();
                    }
                }
            }
            return bitmap;
        }

        /// <summary>
        /// If the pixelSize is bigger than the pixelsPerModule or may end up filling the Module making a traditional QR code.
        /// </summary>
        /// <param name="pixelsPerModule">Pixels used per module rendered</param>
        /// <param name="pixelSize">Size of the dots</param>
        /// <param name="brush">Color of the pixels</param>
        /// <returns></returns>
        private Bitmap MakeDotPixel(int pixelsPerModule, int pixelSize, SolidBrush brush)
        {            
            // draw a dot
            var bitmap = new Bitmap(pixelSize, pixelSize);
            using (var graphics = Graphics.FromImage(bitmap))
            {
                graphics.FillEllipse(brush, new Rectangle(0, 0, pixelSize, pixelSize));
                graphics.Save();
            }

            var pixelWidth = Math.Min(pixelsPerModule, pixelSize);
            var margin = Math.Max((pixelsPerModule - pixelWidth) / 2, 0);

            // center the dot in the module and crop to stay the right size.
            var cropped = new Bitmap(pixelsPerModule, pixelsPerModule);
            using (var graphics = Graphics.FromImage(cropped))
            {
                graphics.DrawImage(bitmap, new Rectangle(margin, margin, pixelWidth, pixelWidth),
                    new RectangleF(((float)pixelSize - pixelWidth) / 2, ((float)pixelSize - pixelWidth) / 2, pixelWidth, pixelWidth),
                    GraphicsUnit.Pixel);
                graphics.Save();
            }

            return cropped;
        }


        /// <summary>
        /// Checks if a given module(-position) is part of the quietzone of a QR code
        /// </summary>
        /// <param name="x">X position</param>
        /// <param name="y">Y position</param>
        /// <param name="numModules">Total number of modules per row</param>
        /// <returns>true, if position is part of quiet zone</returns>
        private bool IsPartOfQuietZone(int x, int y, int numModules)
        {
            return
                x < 4 || //left 
                y < 4 || //top
                x > numModules - 5 || //right
                y > numModules - 5; //bottom                
        }


        /// <summary>
        /// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
        /// </summary>
        /// <param name="x">X position</param>
        /// <param name="y">Y position</param>
        /// <param name="numModules">Total number of modules per row</param>
        /// <param name="offset">Offset in modules (usually depending on drawQuietZones parameter)</param>
        /// <returns>true, if position is part of any finder pattern</returns>
        private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
        {
            var cornerSize = 11 - offset;
            var outerLimitLow = (numModules - cornerSize - 1);
            var outerLimitHigh = outerLimitLow + 8;
            var invertedOffset = 4 - offset;
            return
                (x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
                (x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
                (x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
        }

        /// <summary>
        /// Resize to a square bitmap, but maintain the aspect ratio by padding transparently.
        /// </summary>
        /// <param name="image"></param>
        /// <param name="newSize"></param>
        /// <returns>Resized image as bitmap</returns>
        private Bitmap Resize(Bitmap image, int newSize)
        {
            if (image == null) return null;

            float scale = Math.Min((float)newSize / image.Width, (float)newSize / image.Height);
            var scaledWidth = (int)(image.Width * scale);
            var scaledHeight = (int)(image.Height * scale);
            var offsetX = (newSize - scaledWidth) / 2;
            var offsetY = (newSize - scaledHeight) / 2;

            var scaledImage = new Bitmap(image, new Size(scaledWidth, scaledHeight));

            var bm = new Bitmap(newSize, newSize);

            using (Graphics graphics = Graphics.FromImage(bm))
            {
                using (var brush = new SolidBrush(Color.Transparent))
                {
                    graphics.FillRectangle(brush, new Rectangle(0, 0, newSize, newSize));

                    graphics.InterpolationMode = InterpolationMode.High;
                    graphics.CompositingQuality = CompositingQuality.HighQuality;
                    graphics.SmoothingMode = SmoothingMode.AntiAlias;

                    graphics.DrawImage(scaledImage, new Rectangle(offsetX, offsetY, scaledWidth, scaledHeight));
                }
            }
            return bm;
        }

        /// <summary>
        /// Defines how the quiet zones shall be rendered.
        /// </summary>
        public enum QuietZoneStyle
        {
            Dotted,
            Flat
        }

        /// <summary>
        /// Defines how the background image (if set) shall be rendered.
        /// </summary>
        public enum BackgroundImageStyle
        {
            Fill,
            DataAreaOnly
        }
    }

#if NET6_0_WINDOWS
    [System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
    public static class ArtQRCodeHelper
    {
        /// <summary>
        /// Helper function to create an ArtQRCode graphic with a single function call
        /// </summary>
        /// <param name="plainText">Text/payload to be encoded inside the QR code</param>
        /// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
        /// <param name="darkColor">Color of the dark modules</param>
        /// <param name="lightColor">Color of the light modules</param>
        /// <param name="backgroundColor">Color of the background</param>
        /// <param name="eccLevel">The level of error correction data</param>
        /// <param name="forceUtf8">Shall the generator be forced to work in UTF-8 mode?</param>
        /// <param name="utf8BOM">Should the byte-order-mark be used?</param>
        /// <param name="eciMode">Which ECI mode shall be used?</param>
        /// <param name="requestedVersion">Set fixed QR code target version.</param>
        /// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
        /// <param name="pixelSizeFactor">Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.</param>
        /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
        /// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
        /// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
        /// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
        /// <returns>QRCode graphic as bitmap</returns>
        public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false, 
                                       bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap backgroundImage = null, double pixelSizeFactor = 0.8,
                                       bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat, 
                                       BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null)
        {
            using (var qrGenerator = new QRCodeGenerator())
            using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
            using (var qrCode = new ArtQRCode(qrCodeData))
                return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, backgroundImage, pixelSizeFactor, drawQuietZones, quietZoneRenderingStyle, backgroundImageStyle, finderPatternImage);
        }
    }
}

#endif